/******************************************************************************

	COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2010.

	This file is part of Maxsi Engine.

	Maxsi Engine is free software: you can redistribute it and/or modify it
	under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or (at your
	option) any later version.

	Maxsi engine is distributed in the hope that it will be useful, but WITHOUT
	ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
	FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
	for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with Maxsi Engine. If not, see <http://www.gnu.org/licenses/>.

	MaxsiEngine.dll
	A general purpose C++ library for the Maxsi Technology Project.

	MaxsiDataContainer.cpp
	A very simple binary-safe text-based data container format. The data itself
	has no predefined meaning by this protocol and any meaning should be given
	by any subprotocols.

******************************************************************************/

#include "MaxsiEngine.h"

BeginMaxsiNamespace

MaxsiDataContainer::MaxsiDataContainer()
{
	TemporaryVariable		=	NULL;
	VariableName			=	NULL;		
	Step					=	0;
	ExternalCache			=	NULL;
	InternalCache			=	NULL;
	TemporaryVariableCache	=	NULL;
	ChangedExternalCache	=	false;
}

MaxsiDataContainer::~MaxsiDataContainer()
{
	ClearTemp();
	IFDEL( VariableName );
	IFDEL( InternalCache );
}

bool MaxsiDataContainer::Done()
{
	ClearTemp();
	IFDEL( VariableName );
	IFDEL( InternalCache );
	Step					=	0;
	ExternalCache			=	NULL;
	ChangedExternalCache	=	false;

	return true;
}

bool MaxsiDataContainer::ClearTemp()
{
	if ( TemporaryVariableCache ) { delete[] TemporaryVariableCache; TemporaryVariableCache = NULL; }
	if ( TemporaryVariable ) { DeleteList(TemporaryVariable); TemporaryVariable = NULL; }

	return true;
}

bool MaxsiDataContainer::AppendToTemp(BYTE* Input, size_t Size)
{	
	IFDEL( TemporaryVariableCache );

	return AddListEntry(&TemporaryVariable, Input, Size);
}

BYTE* MaxsiDataContainer::GetTemp()
{
	if ( TemporaryVariableCache ) { return TemporaryVariableCache; }

	return BufferListToBufferN(TemporaryVariable);
}

bool MaxsiDataContainer::GetNextVariable(BYTE* Input, size_t Size, size_t &Read)
{
	ExternalCache	=	NULL;

	if ( InternalCache ) { delete[] InternalCache; InternalCache = NULL; }

	if ( ChangedExternalCache )
	{
		Input[Read]				=	OldExternalCache;
		ChangedExternalCache	=	false;
	}

	if ( Size )
	{
		if ( Step == 0 )
		{
			IFDEL(VariableName);
			ClearTemp();
			Step++;
		}

		if ( Step == 1 )
		{
			// Find the exact location we are reading.
			UTF8_t*	Variable		=	(UTF8_t*)Input+Read;

			// Cast the UTF-8 string to an ASCII string and find the buffer size.
			// If a NUL byte is not found then SIZE_MAX is returned and we don't have
			// the full scring, which works because the NUL byte is only used for
			// NUL termination in UTF-8 as well as ASCII.
			size_t	VariableLen		=	strlen_s((char*)Variable, Size-Read);

			// See if the received the whole variable name yet.
			if ( VariableLen == SIZE_MAX )
			{
				// We haven't recieved it all yet. Just store what we got.
				AppendToTemp(Input+Read, Size-Read);

				MAXSI_TODO_LOW("The return value of AppendToTemp is not checked!");

				Read	+=	Size-Read;

				return false;
			}

			// We have received the entire variable name in UTF-8, now convert it
			// to the internally used string type.
			if ( TemporaryVariable )
			{ 
				UTF8_t*		VariableNameA	=	(UTF8_t*)BuildStringA(2, GetTemp(), Variable);

				MAXSI_TODO_LOW("We need to check if GetTemp() failed and returned NULL!");

				ClearTemp();

				MAXSI_TODO_LOW("VariableNameA is not checked as being a NULL pointer after a failed memory allocation.");

				VariableName	=	UTF82MESTR(VariableNameA);

				MAXSI_TODO_LOW("VariableName is not checked as being a NULL pointer after a failed memory allocation.");

				delete[] VariableNameA;
			}
			else
			{
				VariableName	=	UTF82MESTR(Variable);

				MAXSI_TODO_LOW("VariableName is not checked as being a NULL pointer after a failed memory allocation.	");
			}

			Read	+=	VariableLen+1;

			Step++;
		}

		if ( Step == 2 )
		{
			// We don't parse for UTF-8 here because we are only interested in
			// 0-9, which are all in ASCII.
			char*	VariableLength		=	(char*)Input+Read;
			size_t	VariableLengthLen	=	strlen_s(VariableLength, Size-Read);

			if ( VariableLengthLen == SIZE_MAX )
			{
				// We haven't recieved it all yet. Just store what we got.
				AppendToTemp(Input+Read,Size-Read);

				MAXSI_TODO_LOW("The return value of AppendToTemp is not checked!");

				Read	+=	Size-Read;

				return false;
			}

			if ( TemporaryVariable )
			{ 
				char*	VariableLenSTR	=	BuildStringA(2, GetTemp(), VariableLength);
				VariableLen				=	(size_t)atoi(VariableLenSTR);
				delete[] VariableLenSTR;

				MAXSI_TODO_LOW("We need to check if GetTemp() failed and returned NULL!");
				MAXSI_TODO_LOW("VariableLenSTR is not checked as being a NULL pointer after a failed memory allocation.");

				ClearTemp();
			}
			else
			{
				VariableLen				=	(size_t)atoi(VariableLength);
			}

			VariableLeft				=	VariableLen;

			Read	+=	VariableLengthLen+1;

			Step++;
		}

		if ( Step == 3 )
		{
			if ( VariableLeft <= Size )
			{
				if ( TemporaryVariable == NULL )
				{
					ExternalCache	=	Input+Read;

					// Make sure the variable is zero-terminated,
					// even if we have to make our own local copy of it.
					// Although, avoid memory allocations are much as possible.
					if ( VariableLen )
					{
						if ( ExternalCache[VariableLen-1] != 0 )
						{
							if ( VariableLen < Size-Read )
							{
								// Change the input and restore it later when
								// called again.
								OldExternalCache			=	ExternalCache[VariableLen];
								ChangedExternalCache		=	true;
								ExternalCache[VariableLen]	=	0;
							}
							else
							{
								// There is no room for a zero-termination. Make
								// our own local copy as a last resort.
								InternalCache				=	(BYTE*)new BYTE[VariableLen+1];
								InternalCache[VariableLen]	=	0;
								memcpy(InternalCache,ExternalCache,VariableLen);

								MAXSI_TODO_LOW("InternalCache isn't checked as being a NULL pointer after the memory allocation!");
							}
						}
						else
						{
							// Appears the variable is already zero-terminated. We are safe!
						}
					}
					else
					{
						// Appears we have a zero-length variable. But still ensure that
						// the variable, if requested, is zero-terminated.

						if ( 0 < Size-Read )
						{
							// Change the input and restore it later when
							// called again.
							OldExternalCache			=	ExternalCache[0];
							ChangedExternalCache		=	true;
							ExternalCache[0]			=	0;
						}
						else
						{
							// There is no room for a zero-termination. Make
							// our own local copy as a last resort.
							InternalCache				=	(BYTE*)new char[1];
							MAXSI_TODO_LOW("InternalCache needs to be checked as being a NULL-pointer!");
							InternalCache[0]			=	0;
						}
					}
				}
				else
				{
					AppendToTemp(Input+Read,Size-Read);
					MAXSI_TODO_LOW("Need to check if AppendToTemp failed.");
				}
				
				Step			=	0;
				Read			+=	VariableLeft;
				VariableLeft	=	0;
				return true;	
			}
			else
			{
				AppendToTemp(Input+Read,Size-Read);
				MAXSI_TODO_LOW("Need to check if AppendToTemp failed.");
				VariableLeft	-=	Size-Read;
				Read			+=	Size-Read;
				return false;
			}
		}
	}
	return false;
}

MESTR* MaxsiDataContainer::Variable()
{
	return VariableName;
}

BYTE* MaxsiDataContainer::VariableTemp()
{
	if ( InternalCache )
	{
		return InternalCache;
	}
	else if ( ExternalCache )
	{
		return ExternalCache;
	}
	else
	{
		return GetTemp();
	}
}

MESTR* MaxsiDataContainer::VariableKeep()
{
	return BuildString(1, Variable());
}

EndMaxsiNamespace

